// ============================================================================
// JigClient
// A JigClient implements the client side game communication.
// It runs as a separate thread so as to allow it to keep itself
// up to date with the game server without requiring a time slice
// from the interface class.
//
// CLH April-June 1998
// ============================================================================

package com.chrishobson.jiggle;

public class JigClient extends Thread {

  // ==========================================================================
  // Creates a new Jiggle Client which is basically a
  // socket which is used to perform all of the communication
  // with the gaming server. The socket and worm id are passed
  // to the constructor along with the gaming constants
  // ==========================================================================
  public JigClient(JigSocket sock, int id, int num_food, int num_blocks) {
    m_sock = sock;
    m_id = id;
    m_food = new int[num_food][2];
    m_block = new int[num_blocks][2];
    m_parser = new JigParser();
    m_worms = new JigVector();
  }

  // ==========================================================================
  // Used by the JigGraphics class to request a left move for the worm.
  // The grahical interface has received a left move request from the user
  // send this request to the gaming server. No other action occurs the
  // server decides what to do and will respond in its own time.
  // ==========================================================================
  public void left_move() {
    send("L");
  }

  // ==========================================================================
  // Used by the interface class to request a right move for the worm.
  // ==========================================================================
  public void right_move() {
    send("R");
  }

  // ==========================================================================
  // Used by the interface class to request a game Quit.
  // ==========================================================================
  public void quit_game() {
    send("Q");
  }

  // ==========================================================================
  // Used by the interface class to request a game Join.
  // ==========================================================================
  public void join_game() {
    send("J");
  }

  // ==========================================================================
  // The threads run loop
  // ==========================================================================
  public void run() {
    // We need the entire game status from the server so request it
    send("S");
    while (true) {
      // Only allow an update when the graphics indicate that they
      // have update the display from the last round of moves
      if (m_handled == true) {
        String command = m_sock.get();
        if (command != null) {
          handle(command);
        } else if (m_sock.ok() == false) {
          bye("Connection to server lost");
        }
      }
      try {
        // Client must sleep. Again zappa won't update the graphics
        // Correctly if a yield is used. I don't know why, it works
        // fine on Windows. Is the awt running a lower priority thread
        // which never gets a time slice whilst my threads are running
        // and yielding, perhaps my threads just flip-flop between
        // each other.
        yield(); // sleep(5);
      } catch (Exception e) {
        // yield();
      }
    }
  }

  // ==========================================================================
  // Minor methods just to get and set member data. Made final to give the
  // compiler the option to inline them.
  // ==========================================================================
  public final JigVector get_worms() {
    return m_worms;
  }

  public final int get_update_count() {
    return m_update_count;
  }

  public void handled() {
    m_handled = true;
  }

  public final int num_food() {
    return m_num_food;
  }

  public final int[][] get_food() {
    m_num_food = 0;
    return m_food;
  }

  public final int num_block() {
    return m_num_block;
  }

  public final int[][] get_block() {
    return m_block;
  }

  public final int get_id() {
    return m_id;
  }

  // ======================================================================
  // Method to handle the command string from the server
  // ======================================================================
  private void handle(String command) {
    System.out.println("Handling :" + command);
    m_parser.reset(command);

    while (m_parser.more() == true) {
      char c = m_parser.next_char();
      switch (c) {
      case 'W': {
        // Something has happened to a worm, which one
        int id = m_parser.next_int();
        JigWorm worm = (JigWorm) m_worms.get(id);
        handle_worm(worm, id, m_parser.next_char());
        break;
      }
      // New worm joining the game
      case 'J': {
        // Parse out the Id of the new worm
        int id = m_parser.next_int();
        // Store in the appropriate slot in the array. The worm
        // will parse the rest of its data from the string.
        m_worms.set(new JigWorm(m_parser), id);
        break;
      }
      // New Food
      case 'F': {
        // Parse out the X,Y of the food
        int x = m_parser.next_int();
        int y = m_parser.next_int();
        // Store in the food array
        m_food[m_num_food][0] = x;
        m_food[m_num_food++][1] = y;
        break;
      }
      // New Block
      case 'B': {
        // Parse out the X,Y of the block
        int x = m_parser.next_int();
        int y = m_parser.next_int();
        // Store in the block array
        m_block[m_num_block][0] = x;
        m_block[m_num_block++][1] = y;
        break;
      }
      }
    }
    m_handled = false;
    m_update_count++;
  }

  // ==========================================================================
  // Method to handle a worm specific command string
  // ==========================================================================
  private void handle_worm(JigWorm worm, int id, char c) {
    if (c == 'Q' && id == m_id) {
      bye("You Quit");
    }
    if (worm != null) {
      switch (c) {
      case 'Q': // If it's not us who quit treat it as a death
      case 'D': {
        // Worm has died, set it as dead
        worm.set_dead();
        break;
      }
      case 'R': {
        worm.move_right();
        break;
      }
      case 'L': {
        worm.move_left();
        break;
      }
      case 'F': {
        worm.move_forward();
        break;
      }
      case 'G': {
        worm.grow();
        break;
      }
      }
    }
  }

  // ==========================================================================
  // Allow a slightly more graceful exit by printing a nice message
  // ==========================================================================
  private void bye(String reason) {
    System.out.println("Game Over....");
    System.out.println(reason);
    System.exit(0);
  }

  // ==========================================================================
  // Sends the simple command string to the server
  // ==========================================================================
  void send(String command) {
    m_sock.send(command + m_id);
  }

  // ==========================================================================
  // The member variables, all private
  // ==========================================================================
  private JigSocket m_sock;
  private int m_id;
  private JigParser m_parser;
  private int m_update_count;
  private boolean m_handled = true;
  private int m_food[][];
  private int m_num_food = 0;
  private int m_block[][];
  private int m_num_block = 0;

  // A vector holding all of the worms we know about
  private JigVector m_worms;
}
